package adaptive.ime;

import java.util.GregorianCalendar;
import java.util.List;

import adaptive.opencom.AdaptationRequest;
import adaptive.opencom.OpenComManager;
import adaptive.opencom.log.KeyLog;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;

public class MyKeyboardService extends InputMethodService 
	implements KeyboardView.OnKeyboardActionListener {
	
	public enum KeyboardSize {
		SMALL(0),
		MEDIUM(1),
		LARGE(2);
		
		private int mValue;
		
		KeyboardSize(int value) {
			mValue = value;
		}
		
		public int getValue() {
			return mValue;
		}
	};
	
	public enum KeyboardType {
		QWERTY(0),
		SYMBOLS(1),
		SYMBOLS_SHIFTED(2),
		ELDERLY(3);
		
		private int mValue;
		
		KeyboardType(int value) {
			mValue = value;
		}

		public int getValue() {
			return mValue;
		}
	};
	
	private MyKeyboard mKeyboards[][] = new MyKeyboard[4][3];
	private KeyboardType mCurType;
	private KeyboardSize mCurSize;
	private MyKeyboardView mInputView;
    
	private boolean mCapsLock;
    private long mLastShiftTime;
    private String mWordSeparators;
    private int mLastDisplayWidth;
    
    private OpenComManager ocm; // Our openCom manager
    
    /**
     * Get the current keyboard
     * @return
     */
    private MyKeyboard getCurrentKeyboard() {
    	return mKeyboards[mCurType.getValue()][mCurSize.getValue()];
    }
    
    private MyKeyboard getKeyboard(KeyboardType type, KeyboardSize size) {
    	return mKeyboards[type.getValue()][size.getValue()];
    }
    
    /**
     * Main initialization of the input method component.  Be sure to call
     * to super class.
     */
    @Override
	public void onCreate() {
		super.onCreate();
		mWordSeparators = getResources().getString(R.string.word_separators);
		mCurSize = KeyboardSize.SMALL;
		mCurType = KeyboardType.QWERTY;
		
		//Initiate the OpenCOM Runtime
		ocm = OpenComManager.getInstance();
	}
    
    /**
     * This is the point where you can do all of your UI initialization.  It
     * is called after creation and any configuration change.
     */
	@Override
	public void onInitializeInterface() {
		if(getKeyboard(KeyboardType.QWERTY, KeyboardSize.SMALL) != null) {
			int displayWidth = getMaxWidth();
	        if (displayWidth == mLastDisplayWidth) 
	        	return;
	        
	        mLastDisplayWidth = displayWidth;
		}
		
		// Create the Keyboards
		mKeyboards[KeyboardType.QWERTY.getValue()][KeyboardSize.SMALL.getValue()]
				= new MyKeyboard(this, R.xml.small_qwerty);
		mKeyboards[KeyboardType.QWERTY.getValue()][KeyboardSize.MEDIUM.getValue()]
				= new MyKeyboard(this, R.xml.medium_qwerty);
		mKeyboards[KeyboardType.QWERTY.getValue()][KeyboardSize.LARGE.getValue()]
				= new MyKeyboard(this, R.xml.large_qwerty);
		
		mKeyboards[KeyboardType.SYMBOLS.getValue()][KeyboardSize.SMALL.getValue()]
				= new MyKeyboard(this, R.xml.small_qwerty);
		mKeyboards[KeyboardType.SYMBOLS.getValue()][KeyboardSize.MEDIUM.getValue()]
				= new MyKeyboard(this, R.xml.medium_qwerty);
		mKeyboards[KeyboardType.SYMBOLS.getValue()][KeyboardSize.LARGE.getValue()]
				= new MyKeyboard(this, R.xml.large_qwerty);
		
		mKeyboards[KeyboardType.SYMBOLS_SHIFTED.getValue()][KeyboardSize.SMALL.getValue()]
				= new MyKeyboard(this, R.xml.small_qwerty);
		mKeyboards[KeyboardType.SYMBOLS_SHIFTED.getValue()][KeyboardSize.MEDIUM.getValue()]
				= new MyKeyboard(this, R.xml.medium_qwerty);
		mKeyboards[KeyboardType.SYMBOLS_SHIFTED.getValue()][KeyboardSize.LARGE.getValue()]
				= new MyKeyboard(this, R.xml.large_qwerty);
		/*
		mKeyboards[KeyboardType.ELDERLY.getValue()][KeyboardSize.LARGE.getValue()]
				= new MyKeyboard(this, R.xml.elderly_qwerty);
		*/
	}
	
	/**
     * Called by the framework when your view for creating input needs to
     * be generated.  This will be called the first time your input method
     * is displayed, and every time it needs to be re-created such as due to
     * a configuration change.
     */
	@Override
	public View onCreateInputView() {
		mInputView = (MyKeyboardView) getLayoutInflater().inflate(R.layout.input, null);
		mInputView.setOnKeyboardActionListener(this);
		//mInputView.setKeyboard(getKeyboard(KeyboardType.ELDERLY, KeyboardSize.LARGE));
		mInputView.setKeyboard(getCurrentKeyboard());
		
		return mInputView;
	}
	
	/**
     * This is the main point where we do our initialization of the input method
     * to begin operating on an application.  At this point we have been
     * bound to the client, and are now receiving all of the detailed information
     * about the target of our edits.
     */
	@Override
	public void onStartInput(EditorInfo attribute, boolean restarting) {
		super.onStartInput(attribute, restarting);
		
		// We are now going to initialize our state based on the type of
        // text being edited.
        switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) {
            case EditorInfo.TYPE_CLASS_NUMBER:
            case EditorInfo.TYPE_CLASS_DATETIME:
            case EditorInfo.TYPE_CLASS_PHONE:
                // Phones will also default to the symbols keyboard, though
                // often you will want to have a dedicated phone keyboard.
            	mCurType = KeyboardType.SYMBOLS;
                break;
                
            case EditorInfo.TYPE_CLASS_TEXT:
                // This is general text editing.  We will default to the
                // normal alphabetic keyboard, and assume that we should
                // be doing predictive text (showing candidates as the
                // user types).
            	mCurType = KeyboardType.QWERTY;
            	
                // We also want to look at the current state of the editor
                // to decide whether our alphabetic keyboard should start out
                // shifted.
                //updateShiftKeyState(attribute);
                break;
                
            default:
                // For all unknown input types, default to the alphabetic
                // keyboard with no special features.
            	mCurType = KeyboardType.QWERTY;
                updateShiftKeyState(attribute);
        }
        
        // Update the label on the enter key, depending on what the application
        // says it will do.
        //mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions);
	}
	
	/**
     * This is called when the user is done editing a field.  We can use
     * this to reset our state.
     */
	@Override
	public void onFinishInput() {
		super.onFinishInput();
		
		mCurType = KeyboardType.QWERTY;
		if(mInputView != null)
			mInputView.closing();
	}
	
	@Override
	public void onStartInputView(EditorInfo attribute, boolean restarting) {
		super.onStartInputView(attribute, restarting);
		
		// Apply the selected keyboard to the input view.
        mInputView.setKeyboard(getCurrentKeyboard());
        //mInputView.setKeyboard(getKeyboard(KeyboardType.ELDERLY, KeyboardSize.LARGE));
        mInputView.closing();		
	}
	
	/**
     * Use this to monitor key events being delivered to the application.
     * We get first crack at them, and can either resume them or let them
     * continue to the app.
     */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {	
		switch (keyCode) {
	        case KeyEvent.KEYCODE_BACK:
	            // The InputMethodService already takes care of the back
	            // key for us, to dismiss the input method if it is shown.
	            // However, our keyboard could be showing a pop-up window
	            // that back should dismiss, so we first allow it to do that.
	            if (event.getRepeatCount() == 0 && mInputView != null) {
	                if (mInputView.handleBack()) {
	                    return true;
	                }
	            }
	            break;    
	        case KeyEvent.KEYCODE_ENTER:
	            // Let the underlying text editor always handle these.
	            return false;
	    }		
		return super.onKeyDown(keyCode, event);
	}
	
	/**
     * Use this to monitor key events being delivered to the application.
     * We get first crack at them, and can either resume them or let them
     * continue to the app.
     */
	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		return super.onKeyUp(keyCode, event);
	}
	
	// Implementation of KeyboardView.OnKeyboardActionListener
	public void onKey(int primaryCode, int[] keyCodes) {
		// Log the key press 
		ocm.getEventMonitor().logKey(new KeyLog(new GregorianCalendar(), primaryCode));
		doAdaptation();
		
		if (isWordSeparator(primaryCode)) {
            sendKey(primaryCode);
            updateShiftKeyState(getCurrentInputEditorInfo());
        } else if (primaryCode == Keyboard.KEYCODE_DELETE) {
            handleBackspace();
        } else if (primaryCode == Keyboard.KEYCODE_SHIFT) {
            handleShift();
        } else if (primaryCode == Keyboard.KEYCODE_CANCEL) {
            handleClose();
            return;
        } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE
                && mInputView != null) {
        	Keyboard current = mInputView.getKeyboard();
            
        	if (current == getKeyboard(KeyboardType.SYMBOLS, mCurSize) ||
        		current == getKeyboard(KeyboardType.SYMBOLS_SHIFTED, mCurSize)) {
        		mCurType = KeyboardType.QWERTY;
        	} else {
        		mCurType = KeyboardType.SYMBOLS;
        	}
        	
        	mInputView.setKeyboard(getCurrentKeyboard());
            
        	if (current == getKeyboard(KeyboardType.SYMBOLS, mCurSize)) {
                current.setShifted(false);
            }   	
        } else {        	    	
        	handleCharacter(primaryCode, keyCodes);
        }
	}
	
	/*
	 * Check with there is a adaptation request for the keyboard
	 * */
	private void doAdaptation() {
		List<AdaptationRequest> requests = ocm.getAdaptiveApp().adaptationRequest();
		for(AdaptationRequest r : requests) {
			if(r.getType().equals(AdaptationRequest.AdaptationType.KEYBOARD_SIZE)) {
				//Log.d("AR", r.toString());
				
				mCurSize = (KeyboardSize) r.getParameter();
				Log.d("KEYBOARD_SIZE Adaptation", mCurSize.toString());
				mInputView.setKeyboard(getCurrentKeyboard());
			}
		}
	}
	
	/**
     * Helper to send a key down / key up pair to the current editor.
     */
    private void keyDownUp(int keyEventCode) {
        getCurrentInputConnection().sendKeyEvent(
                new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
        getCurrentInputConnection().sendKeyEvent(
                new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
    }
    
    /**
     * Helper to send a character to the editor as raw key events.
     */
    private void sendKey(int keyCode) {
        switch (keyCode) {
            case '\n':
                keyDownUp(KeyEvent.KEYCODE_ENTER);
                break;
            default:
                if (keyCode >= '0' && keyCode <= '9') {
                    keyDownUp(keyCode - '0' + KeyEvent.KEYCODE_0);
                } else {
                    getCurrentInputConnection().commitText(String.valueOf((char) keyCode), 1);
                }
                break;
        }
    }
    
    /**
     * Helper to update the shift state of our keyboard based on the initial
     * editor state.
     */
    private void updateShiftKeyState(EditorInfo attr) {
        if (attr != null 
                && mInputView != null && getKeyboard(KeyboardType.QWERTY, KeyboardSize.SMALL) == mInputView.getKeyboard()) {
            int caps = 0;
            EditorInfo ei = getCurrentInputEditorInfo();
            if (ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
                caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType);
            }
            mInputView.setShifted(mCapsLock || caps != 0);
        }
    }
    
    private void handleBackspace() {
        keyDownUp(KeyEvent.KEYCODE_DEL);
        
        updateShiftKeyState(getCurrentInputEditorInfo());
    }

    private void handleShift() {
        if (mInputView == null) {
            return;
        }
        
        switch(mCurType) {
        	case QWERTY:
        		checkToggleCapsLock();
            	mInputView.setShifted(mCapsLock || !mInputView.isShifted());
        		break;
        	case SYMBOLS:
        		checkToggleCapsLock();
                mInputView.setShifted(mCapsLock || !mInputView.isShifted());
        		break;
        	case SYMBOLS_SHIFTED:
        		getCurrentKeyboard().setShifted(false);
                mInputView.setKeyboard(getKeyboard(KeyboardType.SYMBOLS, mCurSize));
                getKeyboard(KeyboardType.SYMBOLS, mCurSize).setShifted(false);
        		break;
        }
        
        /*
        Keyboard currentKeyboard = mInputView.getKeyboard();
        if (mQwertyKeyboard == currentKeyboard) {
            // Alphabet keyboard
            checkToggleCapsLock();
            mInputView.setShifted(mCapsLock || !mInputView.isShifted());
        } else if (currentKeyboard == mSymbolsKeyboard) {
            mSymbolsKeyboard.setShifted(true);
            mInputView.setKeyboard(mSymbolsShiftedKeyboard);
            mSymbolsShiftedKeyboard.setShifted(true);
        } else if (currentKeyboard == mSymbolsShiftedKeyboard) {
            mSymbolsShiftedKeyboard.setShifted(false);
            mInputView.setKeyboard(mSymbolsKeyboard);
            mSymbolsKeyboard.setShifted(false);
        }
        */
    }
    
    private void handleCharacter(int primaryCode, int[] keyCodes) {
        if (isInputViewShown()) {
            if (mInputView.isShifted()) {
                primaryCode = Character.toUpperCase(primaryCode);
            }
        }
        getCurrentInputConnection().commitText(
        		String.valueOf((char) primaryCode), 1);
    }

    private void handleClose() {
        requestHideSelf(0);
        mInputView.closing();
    }

    private void checkToggleCapsLock() {
        long now = System.currentTimeMillis();
        if (mLastShiftTime + 800 > now) {
            mCapsLock = !mCapsLock;
            mLastShiftTime = 0;
        } else {
            mLastShiftTime = now;
        }
    }
    
    public boolean isWordSeparator(int code) {
        return mWordSeparators.contains(String.valueOf((char)code));
    }
    
	public void onPress(int primaryCode) {
	}

	public void onRelease(int primaryCode) {		
	}

	public void onText(CharSequence text) {
		InputConnection ic = getCurrentInputConnection();
        if (ic == null) 
        	return;
        
        ic.beginBatchEdit();
        ic.commitText(text, 0);
        ic.endBatchEdit();
        updateShiftKeyState(getCurrentInputEditorInfo());
	}

	public void swipeDown() {
	}

	public void swipeLeft() {
	}

	public void swipeRight() {
	}

	public void swipeUp() {
	}
}
